/*
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.sosolabs.com.site.client.view;

import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.CheckboxCell;
import com.google.gwt.cell.client.CompositeCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.HasCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.MultiSelectionModel;
import com.google.gwt.view.client.SelectionModel;
import com.google.gwt.view.client.TreeViewModel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.sosolabs.com.site.client.view.CellTreeView.Contact;
import com.sosolabs.com.site.client.view.CellTreeView.ContactCell;

/**
 * The {@link TreeViewModel} used to organize contacts into a hierarchy.
 */
public class ContactTreeViewModel implements TreeViewModel {

  /**
   * The images used for this example.
   */
  static interface Images extends ClientBundle {
    ImageResource contact();

    ImageResource contactsGroup();
  }

  public static class Category {

	    private final String displayName;

	    private Category(String displayName) {
	      this.displayName = displayName;
	    }

	    public String getDisplayName() {
	      return displayName;
	    }
	  }
  /**
   * The cell used to render categories.
   */
  private static class CategoryCell extends AbstractCell<Category> {

    /**
     * The html of the image used for contacts.
     */
    private final String imageHtml;

    public CategoryCell(ImageResource image) {
      this.imageHtml = AbstractImagePrototype.create(image).getHTML();
    }

    @Override
    public void render(Category value, Object key, SafeHtmlBuilder sb) {
      if (value != null) {
        sb.appendHtmlConstant(imageHtml).appendEscaped(" ");
        sb.appendEscaped(value.getDisplayName());
      }
    }
  }

  /**
   * Tracks the number of contacts in a category that begin with the same
   * letter.
   */
  private static class LetterCount implements Comparable<LetterCount> {
    private final Category category;
    private final char firstLetter;
    private int count;

    /**
     * Construct a new {@link LetterCount} for one contact.
     *
     * @param category the category
     * @param firstLetter the first letter of the contacts name
     */
    public LetterCount(Category category, char firstLetter) {
      this.category = category;
      this.firstLetter = firstLetter;
      this.count = 1;
    }

    public int compareTo(LetterCount o) {
      return (o == null) ? -1 : (firstLetter - o.firstLetter);
    }

    @Override
    public boolean equals(Object o) {
      return compareTo((LetterCount) o) == 0;
    }

    @Override
    public int hashCode() {
      return firstLetter;
    }

    /**
     * Increment the count.
     */
    public void increment() {
      count++;
    }
  }

  /**
   * A Cell used to render the LetterCount.
   */
  private static class LetterCountCell extends AbstractCell<LetterCount> {

    @Override
    public void render(LetterCount value, Object key, SafeHtmlBuilder sb) {
      if (value != null) {
        sb.appendEscaped(value.firstLetter + " (" + value.count + ")");
      }
    }
  }

  /**
   * The static images used in this model.
   */
  private static Images images;

  private final ListDataProvider<Category> categoryDataProvider;
  private final Cell<Contact> contactCell;
  private final SelectionModel<Contact> selectionModel;

  public ContactTreeViewModel( final SelectionModel<Contact> selectionModel) {
    this.selectionModel = selectionModel;
    if (images == null) {
      images = GWT.create(Images.class);
    }

    // Create a data provider that provides categories.
    categoryDataProvider = new ListDataProvider<Category>();
    List<Category> categoryList = categoryDataProvider.getList();
    Category cat = new Category("family");
    Category cat1 = new Category("cooporation");
    Category cat2 = new Category("friends");
    Category cat3 = new Category("business");
    categoryList.add(cat);
    categoryList.add(cat1);
    categoryList.add(cat2);
    categoryList.add(cat3);

    // Construct a composite cell for contacts that includes a checkbox.
    List<HasCell<Contact, ?>> hasCells = new ArrayList<
        HasCell<Contact, ?>>();
    hasCells.add(new HasCell<Contact, Boolean>() {

      private CheckboxCell cell = new CheckboxCell(true);

      public Cell<Boolean> getCell() {
        return cell;
      }

      public FieldUpdater<Contact, Boolean> getFieldUpdater() {
        return new FieldUpdater<Contact, Boolean>() {
          public void update(int index, Contact object, Boolean value) {
            selectionModel.setSelected(object, value);
          }
        };
      }

      public Boolean getValue(Contact object) {
        return selectionModel.isSelected(object);
      }
    });
    hasCells.add(new HasCell<Contact, Contact>() {

      private ContactCell cell = new ContactCell(images.contact());

      public Cell<Contact> getCell() {
        return cell;
      }

      public FieldUpdater<Contact, Contact> getFieldUpdater() {
        return null;
      }

      public Contact getValue(Contact object) {
        return object;
      }
    });
    contactCell = new CompositeCell<Contact>(hasCells) {
      @Override
      public void onBrowserEvent(Element parent, Contact value, Object key,
          NativeEvent event, ValueUpdater<Contact> valueUpdater) {
        // Make sure that the composition cells see the event.
        super.onBrowserEvent(parent, value, key, event, valueUpdater);

        if ("keyup".equals(event.getType())
            && event.getKeyCode() == KeyCodes.KEY_ENTER) {
          selectionModel.setSelected(value, !selectionModel.isSelected(value));
        }
      }

      @Override
      public void render(Contact value, Object key, SafeHtmlBuilder sb) {
        sb.appendHtmlConstant("<table><tbody><tr>");
        super.render(value, key, sb);
        sb.appendHtmlConstant("</tr></tbody></table>");
      }

      @Override
      protected Element getContainerElement(Element parent) {
        // Return the first TR element in the table.
        return parent.getFirstChildElement().getFirstChildElement().getFirstChildElement();
      }

      @Override
      protected <X> void render(Contact value, Object key, SafeHtmlBuilder sb,
          HasCell<Contact, X> hasCell) {
        Cell<X> cell = hasCell.getCell();
        sb.appendHtmlConstant("<td>");
        cell.render(hasCell.getValue(value), key, sb);
        sb.appendHtmlConstant("</td>");
      }
    };
  }
  
  @Override
  public <T> NodeInfo<?> getNodeInfo(T value) {
    if (value == null) {
      // Return top level categories.
      return new DefaultNodeInfo<Category>(
          categoryDataProvider, new CategoryCell(images.contactsGroup()));
    } else if (value instanceof Category) {
      // Return the first letters of each first name.
      Category category = (Category) value;
          List<Contact> contacts = CONTACTS;
      Map<Character, LetterCount> counts = new TreeMap<
          Character, LetterCount>();
      for (Contact contact : contacts) {
        Character first = contact.name.charAt(0);
        LetterCount count = counts.get(first);
        if (count == null) {
          count = new LetterCount(category, first);
          counts.put(first, count);
        } else {
          count.increment();
        }
      }
      List<LetterCount> orderedCounts = new ArrayList<LetterCount>(
          counts.values());
      return new DefaultNodeInfo<LetterCount>(
          new ListDataProvider<LetterCount>(orderedCounts),
          new LetterCountCell());
    } else if (value instanceof LetterCount) {
      // Return the contacts with the specified character and first name.
      LetterCount count = (LetterCount) value;
          List<Contact> contacts = CONTACTS;
      ListDataProvider<Contact> dataProvider = new ListDataProvider<
          Contact>(contacts);
      return new DefaultNodeInfo<Contact>(
          dataProvider, contactCell, selectionModel, null);
    }

    // Unhandled type.
    String type = value.getClass().getName();
    throw new IllegalArgumentException("Unsupported object type: " + type);
  }

  @Override
  public boolean isLeaf(Object value) {
    return value instanceof Contact;
  }
  
  private static final List<Contact> CONTACTS = Arrays.asList(
		  new Contact("John", new Date(80, 4, 12), "123 Fourth Avenue"),
	      new Contact("Joe", new Date(85, 2, 22), "22 Lance Ln"),
	      new Contact("mary", new Date(85, 2, 22), "23 Lance Ln"),
	      new Contact("tom", new Date(85, 2, 22), "24 Lance Ln"),
	      new Contact("jany", new Date(85, 2, 22), "25 Lance Ln"),
	      new Contact("pony", new Date(85, 2, 22), "26 Lance Ln"),
	      new Contact("tom", new Date(85, 2, 22), "27 Lance Ln"));
  
}
